/*
 * Copyright 2009 XueSong Guo.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package cn.webwheel.database.engine;

import cn.webwheel.ObjectFactory;
import cn.webwheel.database.ConnectionProvider;
import cn.webwheel.database.IException;
import cn.webwheel.database.IPool;

import java.lang.reflect.Modifier;
import java.sql.Connection;
import java.sql.SQLException;

public class IPoolImpl implements IPool {

    static class ConnectionInfo {
        Connection con;
        int nesting = 1;

        public ConnectionInfo(Connection con) throws SQLException {
            (this.con = con).setAutoCommit(false);
        }
    }

    ConnectionProvider connectionProvider;
    ObjectFactory objectFactory;
    IPoolFactory factory;

    private ThreadLocal<ConnectionInfo> cons = new ThreadLocal<ConnectionInfo>();

    public IPoolImpl(IPoolFactory factory, ConnectionProvider connectionProvider, ObjectFactory objectFactory) {
        this.factory = factory;
        if(connectionProvider==null) throw new IllegalArgumentException("null connectionProvider");
        if(objectFactory ==null) throw new IllegalArgumentException("null objectFactory");
        this.connectionProvider = connectionProvider;
        this.objectFactory = objectFactory;
    }

    @SuppressWarnings("unchecked")
    public <T> T get(Class<T> cls) {
        Class<? extends IPoolObject> icls = factory.iclsmap.get(cls);
        if (icls == null) {
            int ms = cls.getModifiers();
            if(!Modifier.isInterface(ms) && !Modifier.isAbstract(ms)) {
                return objectFactory.getInstanceOf(cls);
            }
            try {
                factory.iclsmap.put(cls, icls = new Maker(cls, factory.userTypeInfoMap).create());
            } catch(RuntimeException e) {
                throw e;
            } catch (Exception e) {
                throw new IException(e);
            }
        }

        IPoolObject obj = objectFactory.getInstanceOf(icls);
        obj.setIPoolImpl(this);
        return (T) obj;
    }

    public void startTransaction() throws SQLException {
        ConnectionInfo info = cons.get();
        if (info == null) {
            cons.set(new ConnectionInfo(connectionProvider.get()));
        } else {
            info.nesting++;
        }
    }

    public void commitTransaction() throws SQLException {
        ConnectionInfo info = cons.get();
        if(info==null || info.nesting<=0) throw new IllegalStateException("not in transaction");
        if(--info.nesting==0) {
            info.con.commit();
            info.con.close();
            info.con = null;
            cons.remove();
        }
    }

    public void rollbackTransaction() throws SQLException {
        ConnectionInfo info = cons.get();
        if(info==null || info.nesting<=0) throw new IllegalStateException("not in transaction");
        if(--info.nesting==0) {
            info.con.rollback();
            info.con.close();
            info.con = null;
            cons.remove();
        }
    }

    public Connection getConnection() throws SQLException {
        ConnectionInfo info = cons.get();
        if(info==null || info.nesting<=0) return connectionProvider.get();
        return info.con;
    }

    public void closeConnection(Connection con) throws SQLException {
        ConnectionInfo info = cons.get();
        if(info==null || info.con!=con) con.close();
    }

    public IPoolFactory getFactory() {
        return factory;
    }
}
